Skip to content

Conversation

vtjnash
Copy link
Member

@vtjnash vtjnash commented Sep 30, 2025

Added in #58108 without valid justification: something simply being documented in the manual is never any allowable argument for making something public. Being public is confusing users, since all of the other related infrastructure is also declared private (project files, loaded_modules_array, PkgId, require, etc.). Some of those do have legit uses in reflection testing, but we don't currently have a representation for this sort of private-except-reflection in most places (other than Core.IR).

However is not allowed for packages to call Base.get_extension, since it requires accessing private implementation details of the target package to construct the arguments, and can run afoul of various implementation rules (world ages, module identity, precompile file consistency) that can lead to buggy execution.

The correct way to use this functionality is with dispatch. Loosely:

module Main
function get_extension end
end

module MainPkgExt # extension module in Main
using Other
Main.get_extension(args...) = Other.call(args...)
end

Added in #58108 without valid justification: something
simply being documented in the manual is never any allowable argument
for making something public. Being public is confusing users, since all
of the other related infrastructure is also declared private (project
files, `loaded_modules_array`, `PkgId`, `require`, etc.). Some of those
do have legit uses in reflection testing, but we don't currently have a
representation for this sort of private-except-reflection in most places
(other than `Core.IR`).

However is not allowed for packages to call `Base.get_extension`, since
it requires accessing private implementation details of the target
package to construct the arguments, and can run afoul of various
implementation rules (world ages, module identity, precompile file
consistency) that can lead to buggy execution.

The correct way to use this functionality is with dispatch. Loosely:
```julia
module Main
function get_extension end
end

module MainPkgExt # extension module in Main
using Other
Main.get_extension(args...) = Other.call(args...)
end
```
@vtjnash vtjnash requested a review from KristofferC September 30, 2025 16:08
@vtjnash vtjnash added the backport 1.12 Change should be backported to release-1.12 label Sep 30, 2025
@ericphanson
Copy link
Contributor

since it requires accessing private implementation details of the target package to construct the arguments

is this referring to the name of the extension? (surely the package's module itself is not private?)

@giordano giordano added the needs pkgeval Tests for all registered packages should be run with this change label Sep 30, 2025
@giordano
Copy link
Member

There are hundreds of packages using this already: https://juliahub.com/ui/Search?type=code&q=Base.get_extension

@KristofferC KristofferC removed the backport 1.12 Change should be backported to release-1.12 label Sep 30, 2025
@KristofferC
Copy link
Member

Docstring can be updated to avoid user mistakes.

@vtjnash
Copy link
Member Author

vtjnash commented Sep 30, 2025

That's fine. I am not proposing that we delete it, simply to communicate formally (to lint tools) that this is not an approved way to do things. There is no pkgeval required, since this annotation does not affect runtime.

@vtjnash vtjnash removed the needs pkgeval Tests for all registered packages should be run with this change label Sep 30, 2025
@vtjnash
Copy link
Member Author

vtjnash commented Sep 30, 2025

Docstring can be updated to avoid user mistakes.

Yes, I didn't not write reland or revert since this is the requested doc change.

@vtjnash vtjnash added the docs This change adds or pertains to documentation label Sep 30, 2025
Return the module for `extension` of `parent` or return `nothing` if the extension is not loaded.
Return the module for `extension` relative to `parent` or return `nothing` if the extension is not loaded.
This function is private, since the arguments to it are private implementation details.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In how far are the arguments private?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ext name is private, and the parent argument is used to extract PkgId which is internally also private

@vtjnash
Copy link
Member Author

vtjnash commented Oct 6, 2025

surely the package's module itself is not private

A module is private, other than its own name & the things it declares public internally: a module reference does not give you the public permission to do any arbitrary reflection with it (otherwise the whole public/private distinction is moot). This is a reflection function, and thus simply having the module reference does not imply you can call this function.

@aplavin
Copy link
Contributor

aplavin commented Oct 6, 2025

It's up to package authors to decide whether they consider extension module names public or not. For packages that do, get_extension(ThatPackage, :SomeNameExt) is totally well-defined and relies only on public behavior.

Also, this function can totally legitimately be used in the package defining the extension itself.

@vtjnash
Copy link
Member Author

vtjnash commented Oct 6, 2025

They cannot, since this is private to Base. Particular not in the package itself, since that means the package loads its own extension, which will trigger at lot of issues

@vtjnash vtjnash added the backport 1.12 Change should be backported to release-1.12 label Oct 7, 2025
@vtjnash vtjnash closed this Oct 7, 2025
@vtjnash
Copy link
Member Author

vtjnash commented Oct 7, 2025

Alright, we accidentally made a release with this marked public, so now we have a public API that is forbidden for packages to actually use, since actual use may corrupt their precompile files or crash their users :/

@vtjnash vtjnash reopened this Oct 7, 2025
@vtjnash vtjnash added the merge me PR is reviewed. Merge when all tests are passing label Oct 7, 2025
@DilumAluthge DilumAluthge added the bugfix This change fixes an existing bug label Oct 7, 2025
@DilumAluthge
Copy link
Member

(Bugfix in the sense that adding the public was a bug.)

@aplavin
Copy link
Contributor

aplavin commented Oct 7, 2025

Note that get_extension is explicitly recommended for use by Pkg.jl docs.
Also, get_extension is documented in Julia 1.10 manual, meaning it's public in current LTS and will continue being so.

@aplavin
Copy link
Contributor

aplavin commented Oct 7, 2025

They cannot, since this is private to Base

Not sure what exactly this is in response to – to this?

It's up to package authors to decide whether they consider extension module names public or not

@vtjnash
Copy link
Member Author

vtjnash commented Oct 7, 2025

Also, get_extension is documented in Julia 1.10 manual, meaning it's public in current LTS and will continue being so.

Just be super clear again: being documented never makes something public, unless it is separately declared public. This rule is to prevent something from going undocumented simply because it is only for private use.

@aplavin
Copy link
Contributor

aplavin commented Oct 7, 2025

Being documented in the manual has always been the rule for what's public in Julia, until very recently (only changed in the last version, 1.11).
image
https://docs.julialang.org/en/v1.10/manual/faq/#man-api

@vtjnash
Copy link
Member Author

vtjnash commented Oct 7, 2025

Note that get_extension is explicitly recommended for use by Pkg.jl docs.

I assume you mean recommend against? The Pkg docs mention get_extension in the context of the following sentence:

"On the other hand, accessing extension symbols from a third-party package (i.e. not the parent) is not a recommended practice at the moment."

@aplavin
Copy link
Contributor

aplavin commented Oct 7, 2025

I assume you mean recommend against? The Pkg docs mention get_extension in the context of the following sentence:

There's a couple of sentences just above that, including:
" If extension symbols are needed in the parent package, one must call Base.get_extension to retrieve them."
:)

It's a pretty strong recommendation, using "must".

@vtjnash
Copy link
Member Author

vtjnash commented Oct 7, 2025

Being documented in the manual has always been the rule for what's public in Julia,

Also merged at the same time (these were both added in v1.7) was a statement in the manual that non-exported functions may be mentioned in the manual are still usually private unless the documentation states otherwise (e.g. by adding export or stating it is the API in the documentation):
image
https://github.com/JuliaLang/julia/pull/40533/files#diff-ec04ad384c133e9858008d3947d6eae286ba836280cbae4f6f4dd2ea79b1df00R156

@vtjnash
Copy link
Member Author

vtjnash commented Oct 7, 2025

I am a little confused by what the Pkg.jl authors had in mind, since normally a package wouldn't use its own extension, since why would it be an extension otherwise (it creates a dependency cycle and can create ordering problems with precompilation)

@aplavin
Copy link
Contributor

aplavin commented Oct 7, 2025

Also merged at the same time (these were both added in v1.7) was a statement in the manual that non-exported functions may be mentioned in the manual are still usually private unless the documentation states otherwise (e.g. by adding export or stating it is the API in the documentation):

Note that's the style guide for writing code in Julia :)

@aplavin
Copy link
Contributor

aplavin commented Oct 7, 2025

I am a little confused by what the Pkg.jl authors had in mind, since normally a package wouldn't use its own extension, since why would it be an extension otherwise (it creates a dependency cycle and can create ordering problems with precompilation)

There are quite a few reasonable and legit usages, checking @giordano's link with hundreds of packages using get_extension can be instructive.
For example, get_extension can be used in another extension of the same package.

@vtjnash
Copy link
Member Author

vtjnash commented Oct 7, 2025

Yes, that is what I'm referring to as being known to be fairly buggy and crashy because it causes cycles in the loading resolution code (e.g. the partial fix at #55589, and the many linked issues there, particularly #56204 (comment)) and leads to incorrectly generated cache files.

As @KristofferC said originally "In my view, this should mostly be used as a debugging tool and should not be something that is part of the public API of a package " 😭 #47982 (comment)

@DilumAluthge
Copy link
Member

I need to "update" the branch to fix the trimming failure.

Copy link
Member

@KristofferC KristofferC left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is private, since the arguments to it are private implementation details.

I don't understand what this means. The function is not private and I don't understand what it means that arguments are "private implementation details".

@DilumAluthge DilumAluthge removed the merge me PR is reviewed. Merge when all tests are passing label Oct 7, 2025
@DilumAluthge
Copy link
Member

DilumAluthge commented Oct 7, 2025

(Removing merge me while active review discussion is ongoing, and because there doesn't currently seem to be consensus.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport 1.12 Change should be backported to release-1.12 bugfix This change fixes an existing bug docs This change adds or pertains to documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants